package com.xie.java_doc_searcher.searcher;

import com.xie.java_doc_searcher.config.Config;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 *
 */
public class Parser {
    //先指定一个加载文档的路径

    private static  String INPUT_PATH = null;
    static {
        if (Config.isOnline){
            INPUT_PATH = "/root/doc_searcher_index/jdk-8u333-docs-all/docs/api/";
        }else {
            INPUT_PATH = "D:/doc_searcher_index/jdk-8u333-docs-all/docs/api/";
        }
    }

    // 创建一个Index 实例
    private Index index = new Index();
    private AtomicLong t1 = new AtomicLong(0);
    private AtomicLong t2 = new AtomicLong(0);

    public void run() throws IOException {
        long beg = System.currentTimeMillis();
        // 整个 searcher.Parser 类的入口
        // 1。根据上面指定的路径，来枚举出所有的文件（html） ,这个过程需要把所有子目录中的文件都能获取
        ArrayList<File> fileList = new ArrayList<>();
        long begEnumFile = System.currentTimeMillis();
        enumFile(INPUT_PATH,fileList);
        long endEnumFile = System.currentTimeMillis();
        System.out.println("枚举文件消耗的时间：" +(endEnumFile - begEnumFile) + "ms!");
        // 2，针对上面罗列出的文件的路径，打开文件，读取文件内容，并进行解析，并构建索引
        long begForeachFile = System.currentTimeMillis();

        for (File f:fileList){
            // 通过这个方法来解析单个的 html
//            System.out.println("开始解析" + f.getAbsolutePath());
            parseHtml(f);
        }
        long endForeachFile = System.currentTimeMillis();
        System.out.println("遍历文件消耗的时间:" + (endForeachFile - begForeachFile)+"ms!");

        //TODO 3.把在内存中构建好的索引数据结构，保存到指定的文件中
        index.save();
//        System.out.println(fileList);
//        System.out.println(fileList.size());
        long end = System.currentTimeMillis();
        System.out.println("索引制作的时间 ： " + (end - beg) + "ms!");
    }

    public  void  runBytThread(){
        long beg = System.currentTimeMillis();
        System.out.println("索引制作开始");
        // 枚举文件
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH,files);

        // 循环遍历文件，此处为了能过通过多线程制作索引，就直接引入多线程。
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        CountDownLatch countDownLatch = new CountDownLatch(files.size());
        for (File f : files){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("解析" + f.getAbsolutePath());
                    parseHtml(f);
                    countDownLatch.countDown();
                }
            });
        }
        // 通过 submit 往线程池中提交任务， 是否存在可能性，submit 已经完成了，但是线程池这里还没把这些文档都解析完
        // 所以要等所有的线程完成任务才能
        // 保存索引

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
//        关闭所有的线程
        executorService.shutdown();
        index.save();

        long end = System.currentTimeMillis();
        System.out.println("索引制作结束，消耗时间：" + (end - beg) +   "ms");
        System.out.println("t1 : " + t1 + ", t2 :" + t2);
    }

    // 解析html,就是获取html 文件的 标题描述和URL
    private void parseHtml(File f) {
        // 解析出标题
        String title = parseTitle(f);
        // 解析URL
        String url = parseUrl(f);
        // 解析正文
        long beg = System.nanoTime();
//        String content = parseContent(f);
        String content = parseContentByRegex(f);
        long mid = System.nanoTime();


        // TODO 将解析出来的数据加入索引中
        index.addDoc(title,url,content);
        long end = System.nanoTime();
        //
        t1.addAndGet(mid - beg);
        t2.addAndGet(end - mid);
    }
    // 解析正文

    // html 文件 = html 标签 + 内容
    // 所以去除标签就能得到内容
    // 正则表达式 实现这里的标签
    /*
    按字符读取，对每个字符进行判定，看这个结果是否是< 如果是左 < 那么久从这个位置开始，就知道遇到  > 为止，都不把这些字符放到结果中。

     */
    public String parseContent(File f) {
        StringBuilder content = new StringBuilder();
        // 手动把缓冲区设置成 1M 大小
        try(BufferedReader  bufferedReader =  new BufferedReader(new FileReader(f),1024 * 1024)) {

            // 加上是否拷贝的开关。
            boolean isCopy = true;
            //
            while (true){
                int b = bufferedReader.read();
                char ch = (char) b;
                if (b == -1){
                    break;
                }
                if (ch == '<'){
                    isCopy = false;
                    continue;
                }
                if (ch == '>'){
                    isCopy = true;
                    continue;
                }
                // 去掉换行 \n 和 回车符
                if (ch == '\n' || ch == '\r'){

                    ch = ' ';
                }
                if (isCopy){
                    content.append(ch);
                }
            }
            return content.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    private String readFile(File f){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f))){
            StringBuilder content = new StringBuilder();
            while (true){
                int ret = bufferedReader.read();
                if (ret== -1){
                    break;
                }
                char c = (char)ret;
                if(c == '\n' || c == '\r'){
                    c = ' ';
                }
                content.append(c);
            }
            return content.toString();
        }catch (IOException e){
            e.printStackTrace();
        }
        return "";
    }
    // 这个方法内部就基于正则表达式，实现去标签，以及去除 script
    public String parseContentByRegex(File f){
        // 先读取正文
        String content = readFile(f);
        // 替换Script标签
       content =  content.replaceAll("<script.*?>(.*?)</script>"," ");
        // 替换 html 标签
       content = content.replaceAll("<.*?>"," ");
       // 替换 空格
        content = content.replaceAll("\\s+"," ");
        return content;
    }
    // 解析 url
    private String parseUrl(File f) {
        String part1 = "https://docs.oracle.com/javase/8/docs/api/";
        String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());
        String url = part1+ part2;
        return url;
    }

    //解析文件名
    private String parseTitle(File f) {
//        String title = f.getName().substring(0,f.getName().lastIndexOf('.'));
        String name = f.getName();
        return name.substring(0,name.length()-".html".length());
    }

    // 1.第一个参数表示从哪里开始遍历
    // 2.第二个参数表示递归得到的结果
    private void enumFile(String inputPath, ArrayList<File> fileList)  {
        File rootPath = new File(inputPath);

        // 当rootPath 的类型是 文件类型时就加入到 fileList 中
        // 当 rootPath 的类型是文件夹，就递归 enumFile 这个方法，来获取目录中的内容
        if (rootPath.isFile()){
            // 当前得到的文件不只有 html 文件,通过判断 文件名是否是以 .html 结尾
            if (rootPath.getAbsolutePath().endsWith(".html")) {
                fileList.add(rootPath);
            }
            return;
        }
        // listFiles 能够获取到 当前目录中的所有文件和文件夹
        File[] files = rootPath.listFiles();
        for (File f: files) {
            try {
                enumFile(String.valueOf(f.getCanonicalFile()),fileList);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        //通过main方法来实现整个制作索引的过程
        Parser parser = new Parser();
        parser.runBytThread();

    }

}
